<?php
/**
 * @package base
 */

/** Pattern of a typehint message which creates php itself */
define('TYPEHINT_PCRE', '/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/');

/**
 * Provide typehint - function's real argument types check.
 * Even for primitive types which aren't supported by default.
 * 
 * This should be set off in production environment due to performance.
 * 
 * @filesource www.php.net
 */
class Typehint {
	/** allowed name for the string primitive type */
	const STRING = "string";
	/** allowed name for the boolean primitive type */
	const BOOL = "bool";
	/** allowed name for the integer primitive type */
	const INT = "int";
	/** allowed name for the float primitive type */
	const FLOAT = "float";
	/** allowed name for the resource primitive type */
	const RESOURCE = "resource";
	
	/** functions that decides wheter a primitive type is or isn't what we wanted */
	private static $Typehints = array(
        self::BOOL 		=> 'is_bool',
        self::INT  		=> 'is_int',
        self::FLOAT		=> 'is_float',
        self::STRING    => 'is_string',
        self::RESOURCE  => 'is_resource'
    );
	
    /** This class is used only once */
    private function __construct() {}
	
    /** Override php's default error_handler */
    public static function initializeHandler() {
		set_error_handler('Typehint::handleTypehint');
		return true;
    }
	
    private static function getTypehintedArgument($ThBackTrace, $ThFunction, $ThArgIndex, &$ThArgValue) {
     foreach ($ThBackTrace as $ThTrace) {
     	// Match the function; Note we could do more defensive error checking.
     	if (isset($ThTrace['function']) && $ThTrace['function'] == $ThFunction) {
     		$ThArgValue = $ThTrace['args'][$ThArgIndex - 1];
     		return true;
     	}
     }

     return false;
    }

    /**
     * Method, which is executed before any other function/method execution and for it's every parameter.
     * 
     * @return bool
     */
    public static function handleTypehint($ErrLevel, $ErrMessage) {
     if ($ErrLevel == E_RECOVERABLE_ERROR) {
     	// Get content of the error message.
     	if (preg_match(TYPEHINT_PCRE, $ErrMessage, $ErrMatches)) {
     		list($ErrMatch, $ThArgIndex, $ThClass, $ThFunction, $ThHint, $ThType) = $ErrMatches;
     		// Check if there is test function defined for given Type Hint.
     		if (isset(self::$Typehints[$ThHint])) {

     			$ThBacktrace = debug_backtrace();
     			$ThArgValue  = NULL;
		
     			// Get argument value for the appopriate function/method
     			if (self::getTypehintedArgument($ThBacktrace, $ThFunction, $ThArgIndex, $ThArgValue)) {
     				// Check if value is of a type specified in type hint.
     				if (call_user_func(self::$Typehints[$ThHint], $ThArgValue) ||
     						$ThHint == self::STRING && $ThType == "null") {
     					return TRUE;
     				}
     			}
     		}
     	}
     }
    return FALSE;
	}
}

Typehint::initializeHandler();

?>
